package mrpanyu.guitool.base.view.swing;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingConstants;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLDocument;

import mrpanyu.guitool.base.model.Action;
import mrpanyu.guitool.base.model.Parameter;
import mrpanyu.guitool.base.model.ParameterType;
import mrpanyu.guitool.base.model.Tool;
import mrpanyu.guitool.base.model.ToolModel;
import mrpanyu.guitool.base.util.HelpDocBuilder;
import mrpanyu.guitool.base.util.Profile;
import mrpanyu.guitool.base.util.Profiles;

public class GuiToolPanelBuilder extends Tool {

	private GuiConfig guiConfig = new GuiConfig();

	private ImageIcon iconFolder = new ImageIcon(this.getClass().getResource("images/folder.png"));
	private ImageIcon iconFloppy = new ImageIcon(this.getClass().getResource("images/floppy.png"));
	private ImageIcon iconTrash = new ImageIcon(this.getClass().getResource("images/trash.png"));
	private ImageIcon iconHelp = new ImageIcon(this.getClass().getResource("images/help.png"));

	private Font normalFont;
	private Font boldFont;

	private JPanel mainPanel;
	private JPanel panelTop;
	private JPanel panelCenter;
	private Map<String, JComponent> parameterComponentMap = new LinkedHashMap<>();
	private Map<String, JPanel> parameterPanelMap = new LinkedHashMap<>();
	private Map<String, JButton> actionButtonMap = new LinkedHashMap<>();
	private JTextPane textOutput;
	private boolean refreshing = false;
	private String helpContent;
	private Profiles profiles;
	private JComboBox<String> comboProfiles;

	public GuiToolPanelBuilder(ToolModel toolModel) {
		this(toolModel, null);
	}

	public GuiToolPanelBuilder(ToolModel toolModel, GuiConfig guiConfig) {
		super(toolModel);
		if (guiConfig != null) {
			this.guiConfig = guiConfig;
		}
		initialize();
	}

	public void onload() {
		toolModel.onload(this);
		this.refreshInput();
	}

	private void initialize() {
		initializeIconsAndFonts();

		// basic frame layout
		mainPanel = new JPanel();
		mainPanel.setLayout(new BorderLayout());
		// panel for parameters and action buttons
		panelTop = new JPanel();
		panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS));
		mainPanel.add(panelTop, BorderLayout.NORTH);
		// panel for output
		panelCenter = new JPanel();
		panelCenter.setLayout(new BoxLayout(panelCenter, BoxLayout.X_AXIS));
		mainPanel.add(panelCenter, BorderLayout.CENTER);

		// add profiles combo if enabled
		initializeProfiles();

		// initialize parameter inputs
		initializeParameters();

		// initialize buttons
		initializeButtons();

		// initialize output text
		initializeTextOutput();

		// finalize frame configuration
		mainPanel.setPreferredSize(new Dimension(guiConfig.getWindowWidth(), guiConfig.getWindowHeight()));
	}

	private void initializeIconsAndFonts() {
		// icons
		int imageSize = guiConfig.getButtonImgSize();
		iconFolder.setImage(iconFolder.getImage().getScaledInstance(imageSize, imageSize, Image.SCALE_SMOOTH));
		iconFloppy.setImage(iconFloppy.getImage().getScaledInstance(imageSize, imageSize, Image.SCALE_SMOOTH));
		iconTrash.setImage(iconTrash.getImage().getScaledInstance(imageSize, imageSize, Image.SCALE_SMOOTH));
		iconHelp.setImage(iconHelp.getImage().getScaledInstance(imageSize, imageSize, Image.SCALE_SMOOTH));
		// fonts
		normalFont = new Font("", 0, guiConfig.getFontSize());
		boldFont = new Font("", Font.BOLD, guiConfig.getFontSize());
	}

	private void initializeProfiles() {
		if (toolModel.isEnableProfiles()) {
			profiles = new Profiles(toolModel.getDisplayName());
			profiles.load();
			JPanel panel = new JPanel();
			panelTop.add(panel);
			BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
			panel.setLayout(layout);
			JLabel label = new JLabel("Profiles: ");
			label.setFont(boldFont);
			label.setHorizontalAlignment(SwingConstants.RIGHT);
			label.setPreferredSize(new Dimension(guiConfig.getLabelWidth(), guiConfig.getLineHeight()));
			panel.add(label);
			comboProfiles = new JComboBox<>();
			comboProfiles.setFont(normalFont);
			comboProfiles.addItemListener(new ItemListener() {
				public void itemStateChanged(ItemEvent e) {
					if (e.getStateChange() == ItemEvent.SELECTED) {
						loadProfile();
					}
				}
			});
			panel.add(comboProfiles);
			JButton button = new JButton(iconFloppy);
			button.setFont(boldFont);
			button.setPreferredSize(new Dimension(guiConfig.getLineHeight(), guiConfig.getLineHeight()));
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					saveProfile();
				}
			});
			panel.add(button);
			button = new JButton(iconTrash);
			button.setFont(boldFont);
			button.setPreferredSize(new Dimension(guiConfig.getLineHeight(), guiConfig.getLineHeight()));
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					deleteProfile();
				}
			});
			panel.add(button);
			refreshComboProfiles();
		}
	}

	private void initializeParameters() {
		List<Parameter> parameters = toolModel.getParameters();
		for (Parameter p : parameters) {
			final Parameter parameter = p;
			JPanel panel = new JPanel();
			parameterPanelMap.put(parameter.getName(), panel);
			panel.setVisible(parameter.isVisible());
			panelTop.add(panel);
			BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
			panel.setLayout(layout);
			JLabel label = new JLabel(parameter.getDisplayName() + ": ");
			label.setFont(boldFont);
			label.setHorizontalAlignment(SwingConstants.RIGHT);
			label.setPreferredSize(new Dimension(guiConfig.getLabelWidth(), guiConfig.getLineHeight()));
			panel.add(label);
			if (parameter.getType() == ParameterType.SELECT) {
				final JComboBox<String> combo = new JComboBox<>();
				combo.setFont(normalFont);
				for (String value : parameter.getOptions()) {
					combo.addItem(value);
				}
				if (parameter.getValue() != null && !parameter.getValue().isEmpty()) {
					combo.setSelectedItem(parameter.getValue());
				}
				combo.addItemListener(new ItemListener() {
					public void itemStateChanged(ItemEvent e) {
						if (e.getStateChange() == ItemEvent.SELECTED && !refreshing) {
							parameter.setValue((String) combo.getSelectedItem());
							if (parameter.getChangeHandler() != null) {
								try {
									parameter.getChangeHandler().onParameterChange(parameter, GuiToolPanelBuilder.this);
								} catch (Throwable t) {
									t.printStackTrace();
									errorMessage(t);
								}
								refreshInput();
							}
						}
					}
				});
				panel.add(combo);
				parameterComponentMap.put(parameter.getName(), combo);
			} else if (parameter.getType() == ParameterType.MULTILINE_TEXT
					|| parameter.getType() == ParameterType.MULTILINE_TEXT_LINEWRAP) {
				final JTextArea text = new JTextArea();
				text.setFont(normalFont);
				text.setTabSize(4);
				if (parameter.getType() == ParameterType.MULTILINE_TEXT_LINEWRAP) {
					text.setLineWrap(true);
				}
				JScrollPane scrollPane = new JScrollPane(text);
				scrollPane.setPreferredSize(new Dimension(0, 100));
				scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
				scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
				if (parameter.getValue() != null && !parameter.getValue().isEmpty()) {
					text.setText(parameter.getValue());
				}
				text.addFocusListener(new FocusAdapter() {
					public void focusLost(FocusEvent e) {
						if (!text.getText().equals(parameter.getValue())) {
							parameter.setValue(text.getText());
							if (parameter.getChangeHandler() != null) {
								try {
									parameter.getChangeHandler().onParameterChange(parameter, GuiToolPanelBuilder.this);
								} catch (Throwable t) {
									t.printStackTrace();
									errorMessage(t);
								}
								refreshInput();
							}
						}
					}
				});
				panel.add(scrollPane);
				parameterComponentMap.put(parameter.getName(), text);
			} else {
				final JTextField text = new JTextField();
				text.setFont(normalFont);
				if (parameter.getValue() != null && !parameter.getValue().isEmpty()) {
					text.setText(parameter.getValue());
				}
				text.addFocusListener(new FocusAdapter() {
					public void focusLost(FocusEvent e) {
						if (!text.getText().equals(parameter.getValue())) {
							parameter.setValue(text.getText());
							if (parameter.getChangeHandler() != null) {
								try {
									parameter.getChangeHandler().onParameterChange(parameter, GuiToolPanelBuilder.this);
								} catch (Throwable t) {
									t.printStackTrace();
									errorMessage(t);
								}
								refreshInput();
							}
						}
					}
				});
				panel.add(text);
				parameterComponentMap.put(parameter.getName(), text);
				if (parameter.getType() == ParameterType.DIRECTORY || parameter.getType() == ParameterType.FILE) {
					JButton button = new JButton(iconFolder);
					button.setPreferredSize(new Dimension(guiConfig.getLineHeight(), guiConfig.getLineHeight()));
					button.addActionListener(new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							openFolderOrFile(parameter, text);
						}
					});
					panel.add(button);
				}
			}
		}
	}

	private void initializeButtons() {
		JPanel panel = new JPanel();
		panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
		panelTop.add(panel);
		BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
		panel.setLayout(layout);

		// action buttons
		for (Action a : toolModel.getActions()) {
			final Action action = a;
			final JButton button = new JButton(action.getDisplayName());
			button.setFont(boldFont);
			button.setVisible(action.isVisible());
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					new Thread(new Runnable() {
						public void run() {
							try {
								action.getHandler().handle(GuiToolPanelBuilder.this);
							} catch (Throwable t) {
								t.printStackTrace();
								errorMessage(t);
							}
							refreshInput();
						}
					}).start();
				}
			});
			panel.add(button);
			actionButtonMap.put(action.getName(), button);
		}

		// help button
		helpContent = HelpDocBuilder.buildHelp(toolModel);
		JButton buttonHelp = new JButton("Help");
		buttonHelp.setIcon(iconHelp);
		buttonHelp.setFont(boldFont);
		buttonHelp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				showHelp();
			}
		});
		panel.add(buttonHelp);
	}

	private void initializeTextOutput() {
		textOutput = new JTextPane();
		textOutput.setEditable(false);
		textOutput.setContentType("text/html");
		textOutput.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
		textOutput.setText("<html><body id='body'></body></html>");
		JScrollPane scrollPane = new JScrollPane(textOutput);
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		panelCenter.add(scrollPane);
	}

	private void openFolderOrFile(Parameter parameter, JTextField text) {
		String currentPath = text.getText();
		JFileChooser jfc = new JFileChooser(new File(currentPath).getParentFile());
		if (parameter.getType() == ParameterType.DIRECTORY) {
			jfc.setAcceptAllFileFilterUsed(false);
			jfc.setFileFilter(new FileFilter() {
				public String getDescription() {
					return "Folder";
				}

				public boolean accept(File f) {
					return f.isDirectory();
				}
			});
			jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		} else {
			jfc.setAcceptAllFileFilterUsed(true);
			jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		}
		int result = jfc.showOpenDialog(mainPanel);
		if (result == JFileChooser.APPROVE_OPTION) {
			File f = jfc.getSelectedFile();
			String path = f.getAbsolutePath();
			text.setText(path);
			parameter.setValue((String) text.getText());
			if (parameter.getChangeHandler() != null) {
				try {
					parameter.getChangeHandler().onParameterChange(parameter, GuiToolPanelBuilder.this);
				} catch (Throwable t) {
					t.printStackTrace();
					errorMessage(t);
				}
				refreshInput();
			}
		}
	}

	public JPanel getJPanel() {
		return this.mainPanel;
	}

	public void debugMessage(String message) {
		if (isDebugEnabled()) {
			writeMessage("DEBUG", message, "gray");
		}
	}

	public void infoMessage(String message) {
		writeMessage("INFO ", message, "blue");
	}

	public void warnMessage(String message) {
		writeMessage("WARN ", message, "#606000");
	}

	public void errorMessage(String message) {
		writeMessage("ERROR", message, "red");
	}

	public void errorMessage(Throwable t) {
		if (t == null) {
			return;
		}
		try (StringWriter writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer);) {
			t.printStackTrace(pw);
			String message = writer.toString();
			errorMessage(message);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void writeMessage(final String level, final String message, final String color) {
		if (message == null) {
			return;
		}
		SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
		String timeStr = fmt.format(new Date());

		String divStart = "<div style=\"color:" + color
				+ ";white-space:nowrap;font-family:Consolas,monospace;font-size:" + guiConfig.getMessageFontSize()
				+ "px\">";
		String html = divStart + "[" + timeStr + "][" + level + "]" + escapeHtml(message).replace("\r\n", "\n")
				.replace("\n", "</div>" + divStart).replace("\t", "    ").replace(" ", "&nbsp;") + "</div>";
		htmlMessage(html);
	}

	private String escapeHtml(String message) {
		return message.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
	}

	public void htmlMessage(final String message) {
		if (message == null) {
			return;
		}
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					HTMLDocument doc = (HTMLDocument) textOutput.getDocument();
					Element body = doc.getElement("body");
					doc.insertBeforeStart(body.getElement(body.getElementCount() - 1), message);
					while (body.getElementCount() > guiConfig.getMaxLogRows()) {
						doc.removeElement(body.getElement(0));
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public void clearMessages() {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				textOutput.setText("<html><body id='body'></body></html>");
			}
		});
	}

	private void refreshInput() {
		EventQueue.invokeLater(new Runnable() {
			@SuppressWarnings("unchecked")
			public void run() {
				refreshing = true;
				for (Parameter parameter : toolModel.getParameters()) {
					JComponent component = parameterComponentMap.get(parameter.getName());
					if (component instanceof JComboBox) {
						String parameterValue = parameter.getValue();
						JComboBox<String> combo = (JComboBox<String>) component;
						combo.removeAllItems();
						for (String value : parameter.getOptions()) {
							combo.addItem(value);
						}
						combo.setSelectedItem(parameterValue);
					} else if (component instanceof JTextArea) {
						JTextArea text = (JTextArea) component;
						text.setText(parameter.getValue());
					} else if (component instanceof JTextField) {
						JTextField text = (JTextField) component;
						text.setText(parameter.getValue());
					}
					JPanel panel = parameterPanelMap.get(parameter.getName());
					panel.setVisible(parameter.isVisible());
				}
				for (Action action : toolModel.getActions()) {
					JButton button = actionButtonMap.get(action.getName());
					button.setVisible(action.isVisible());
				}
				refreshing = false;
			}
		});
	}

	private void showHelp() {
		JFrame frameHelp = new JFrame("Help");
		JTextPane textHelp = new JTextPane();
		textHelp.setEditable(false);
		textHelp.setContentType("text/html");
		textHelp.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
		textHelp.setText(helpContent);
		JScrollPane scrollPane = new JScrollPane(textHelp);
		int width = guiConfig.getWindowWidth();
		int height = guiConfig.getWindowHeight();
		scrollPane.setPreferredSize(new Dimension(width, height));
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		frameHelp.getContentPane().add(scrollPane, BorderLayout.CENTER);
		frameHelp.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frameHelp.setIconImage(iconHelp.getImage());
		frameHelp.pack();
		frameHelp.setLocationByPlatform(false);
		frameHelp.setLocationRelativeTo(null);
		frameHelp.setVisible(true);
	}

	private void refreshComboProfiles() {
		String oldName = (String) comboProfiles.getSelectedItem();
		String selectName = "";
		comboProfiles.removeAllItems();
		comboProfiles.addItem("");
		for (String name : profiles.getProfileNames()) {
			comboProfiles.addItem(name);
			if (name.equals(oldName)) {
				selectName = name;
			}
		}
		comboProfiles.setSelectedItem(selectName);
	}

	private void loadProfile() {
		String profileName = (String) comboProfiles.getSelectedItem();
		if (profileName != null && profileName.trim().length() > 0) {
			Profile profile = profiles.getProfile(profileName);
			for (Map.Entry<String, String> entry : profile.getParameterValues().entrySet()) {
				String paramName = entry.getKey();
				String paramValue = entry.getValue();
				Parameter parameter = getParameter(paramName);
				parameter.setValue(paramValue);
				if (parameter.getChangeHandler() != null) {
					try {
						parameter.getChangeHandler().onParameterChange(parameter, GuiToolPanelBuilder.this);
					} catch (Throwable t) {
						t.printStackTrace();
						errorMessage(t);
					}
				}
			}
			refreshInput();
		}
	}

	private void saveProfile() {
		String profileName = (String) comboProfiles.getSelectedItem();
		profileName = JOptionPane.showInputDialog(mainPanel, "Save Profile As: ", profileName);
		if (profileName != null && profileName.trim().length() > 0) {
			Profile profile = new Profile(profileName);
			for (Parameter parameter : toolModel.getParameters()) {
				profile.getParameterValues().put(parameter.getName(), parameter.getValue());
			}
			profiles.setProfile(profileName, profile);
			profiles.save();
			refreshComboProfiles();
			comboProfiles.setSelectedItem(profileName);
		}
	}

	private void deleteProfile() {
		String profileName = (String) comboProfiles.getSelectedItem();
		if (profileName != null && profileName.trim().length() > 0) {
			if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(mainPanel, "Delete this profile?", "Confirm",
					JOptionPane.YES_NO_OPTION)) {
				profiles.removeProfile(profileName);
				profiles.save();
				refreshComboProfiles();
			}
		}
	}

}
